此範例參照Solidity 其中一個example,使用環境設定在投票現場,可能是多位參選人、多個提案...,主辦方將每個參選項目轉換成32bytes,集合成array,一併放到合約,投票者可以根據項目序號投給支持的對象。在這份合約中,主辦方可以給予指定的人投票權,投票者可以自己投票也可以託人投票,不過託人投票只限定在雙方有共同支持的對象,投票時間結束,找到投票數最多的項目。
pragma solidity^0.4.25;
contract Vote{
struct Voter{
uint weight; //投票數
bool voted;
address delegate;
uint vote;
}
struct Proposal{
bytes32 name;
uint voteCount;
}
address public chairperson;
mapping(address=> Voter) public voters;
Proposal[] public proposals;
constructor(bytes32[] proposalName) public{
chairperson = msg.sender;
voters[chairperson].weight = 1;
//將每個提案hash push 到proposals變數
for(uint i = 0; i< proposalName.length ; i++){
proposals.push(Proposal(proposalName[i],0));
}
}
//給予投票權
function getRightToVote(address voter) public {
require(msg.sender == chairperson,"你不是發起人");
require(!voters[voter].voted,"指定的voter已經投過票了");
require(voters[voter].weight == 0,"這個voter已經有投票權");
voters[voter].weight = 1;
}
//託人投票
function delegate(address _to) public{
require(!voters[msg.sender]].voted,"你已經投過票了");
require(_to != msg.sender,"不行指定本人");
while(voters[_to].delegate == address(0)){
_to = voters[_to].delegate;
require(_to != msg.sender );
}
voters[msg.sender].voted = true;
voters[msg.sender].delegate = _to;
if(!voters[_to].voted){
proposals[voters[_to].vote].voteCount += voters[msg.sender].weight;
}else{
voters[_to].weight += voters[msg.sender].weight;
}
}
//投票
function vote(uint _proposalIndex) public{
require(!voters[msg.sender].voted,"已經投過票了");
voters[msg.sender].voted = true;
voters[msg.sender].vote = _proposalIndex;
proposals[_proposalIndex].voteCount += voters[msg.sender].weight;
}
//計算最多票
function winnerProposal() public view returns(uint winner){
require(msg.sender == chairperson,"你不是發起人");
uint winnerCount;
for(uint i = 0; i < proposals.length ; i++){
if(proposals[i].voteCount > winnerCount){
winnerCount = proposals[i].voteCount;
winner = i;
}
}
}
}
struct Voter{
uint weight; //投票數
bool voted;
address delegate;
uint vote;
}
struct Proposal{
bytes32 name;
uint voteCount;
}
address public chairperson;
mapping(address=> Voter) public voters;
Proposal[] public proposals;
constructor(bytes32[] proposalName) public{
chairperson = msg.sender;
voters[chairperson].weight = 1;
//將每個提案hash push 到proposals變數
for(uint i = 0; i< proposalName.length ; i++){
proposals.push(Proposal(proposalName[i],0));
}
}
struct
分別代表Voter投票者和Proposal投票方案,mapping voters
表示投票者的address
對應到struct Voter
的資料結構,合約內的每一個投票方案會被集中在porposal array
,並且以struct Proposal
紀錄方案名稱及獲得的票數。address chairperson
為主辦單位的身分,可以給予投票權,及宣布獲選方案。//給予投票權
function getRightToVote(address voter) public {
require(msg.sender == chairperson,"你不是發起人");
require(!voters[voter].voted,"指定的voter已經投過票了");
require(voters[voter].weight == 0,"這個voter已經有投票權");
voters[voter].weight = 1;
}
getRightToVote(address voter)
為主辦方給予address參數投票權的function執行function時會判斷執行function的身分是不是發起人、判斷參數有無投過票、判斷有沒有投票權,如果都符合條件,指定的address就可以獲得投票權
//託人投票
function delegate(address _to) public{
require(!voters[msg.sender].voted,"你已經投過票了");
require(_to != msg.sender,"不行指定本人");
while(voters[_to].delegate == address(0)){
_to = voters[_to].delegate;
require(_to != msg.sender );
}
voters[msg.sender].voted = true;
voters[msg.sender].delegate = _to;
if(!voters[_to].voted){
proposals[voters[_to].vote].voteCount += voters[msg.sender].weight;
}else{
voters[_to].weight += voters[msg.sender].weight;
}
}
delegate(address _to)
為託人投票的function先判斷被代理人有沒有投過票,並且參數不能指定被代理人,符合條件後,如果代理人Voter struct
裏頭delegate
的欄位是空的,將目前,將被代理人的voted投票狀態
改為true
,delegate
填入代理人的address
,並判斷代理人有沒有投過票,如果為true
將票一併投給代理人支持的方案序號,如果為false
代理人多獲得一次投票權
//投票
function vote(uint _proposalIndex) public{
require(!voters[msg.sender].voted,"已經投過票了");
voters[msg.sender].voted = true;
voters[msg.sender].vote = _proposalIndex;
proposals[_proposalIndex].voteCount += voters[msg.sender].weight;
}
//計算最多票
function winnerProposal() public view returns(uint winner){
require(msg.sender == chairperson,"你不是發起人");
uint winnerCount;
for(uint i = 0; i < proposals.length ; i++){
if(proposals[i].voteCount > winnerCount){
winnerCount = proposals[i].voteCount;
winner = i;
}
}
}
vote()
為投票function,輸入支持的投票方案序號,即代表投給對應的投票方案判斷當前執行function的address有沒有投過票,如果還沒投票,Voter struct
的voted 投票狀態 = true
,vote = 支持的投票方案序號
並將當前address的投票數,轉移給mapping proposals
對應的方案序號
winnerProposal()
為計算最多票的function先判斷是不是發起人,再來for迴圈
會將所有的proposal獲得的票數做比對,並以參數winnerCount
紀錄最多票,最後回傳得最多票的方案序號,公布獲勝方案
address public Host; //發起人-主辦單位
uint256 public TicketPrice; //當日票價
uint256 public MaxParticipants; //當日票數
uint256 public SalesTime; //當日販售結束時間
uint256 public SalesDate; //當日販售日期
uint256 public amountOfParticipant; //參與者數量
mapping(address => bytes32[]) public tickets; //value為byts32陣列,一個address可以買多張票,每個票都有一個tickets hash
mapping(uint => address) public IdToUser; //票券ID對應的購買者
constructor(uint256 _price, uint256 _participants, uint256 _time) public{
Host = msg.sender;
TicketPrice = _price*10**15;
MaxParticipants = _participants;
SalesTime = now + _time;
SalesDate = now;
}
發起合約depoly contract時,需要輸入當日售票票價(以finney為計價單位,1 finney
== 0.001 ether
)、當日票券總數量(以參與者數量計算)、販售時間(以毫秒為單位)
function () external payable{
buyTikcet();
}
function buyTikcet() private{
require(now <= SalesTime,"超出時間"); //確認有無超過販售時間
require(amountOfParticipant < MaxParticipants,"今日票價已售完"); //確認沒有超過總售票量
require(msg.value == TicketPrice,"不符合票價"); //確認支付的錢跟售價一樣
amountOfParticipant++;
//插入轉換hash function
bytes32 ticketHash = keccak256(abi.encodePacked(amountOfParticipant,msg.sender)); //將userId和user address一同hash
tickets[msg.sender].push(ticketHash); //紀錄
IdToUser[amountOfParticipant] = msg.sender;
}
fallback function
並支付發起合約時所訂的價格,一次買一張票。fallback function
為external
,代表只提供給外部call function,搭配payable
執行時需要支付一筆ether;buyTicket()
function 為private
代表只能在合約內部執行且不公開,當fallback function執行時,會執行buyTicket()
function,判斷目前有沒有超過販售時間、有沒有剩餘的票券、支付的錢有沒有符合售票價格。通過條件判斷之後,增加參與者數量,以keccak256 hash function
將 參與者數量(也可以當作票券ID) 和買票的address 共同HASH,產生出32bytes的票券hash value,作為當日驗票的證明。最後,票券hash value 會push到 mapping tickets[msg.sender]
紀錄在買票的address的bytes32 array
function verify(bytes32 _ticketHash) public view returns(bool){
for(uint i = 0 ; i < tickets[msg.sender].length ; i++){
if(_ticketHash == tickets[msg.sender][i]){
return true;
}
}
}
function withdraw() public{
Host.transfer(address(this).balance);
}
在verify()
function 輸入票券hash,for loop
會先根據mapping tickets[msg.sender]
找到目前呼叫function的address,所擁有的票券hash,如果輸入的參數符合address擁有的票券之一,回傳true